import frappe
from frappe.utils import flt
from pypika.terms import Tuple
from frappe.model.mapper import get_mapped_doc


def delete_draft_warehouse_task(doc, method):
    frappe.db.delete('Warehouse Task', {
        'voucher_type': doc.doctype,
        'voucher_no': doc.name,
        'docstatus': 0
    })

def update_warehouse_task(doc, method):
    """
    只需要处理on_submit, cancel时系统自动处理级联取消
    """
    if doc.doctype == 'Stock Reconciliation':
        if any(True for row in doc.items if flt(row.quantity_difference) < 0):
            create_warehouse_task(doc, "Pick Up")
        if any(True for row in doc.items if flt(row.quantity_difference) > 0):
            create_warehouse_task(doc, "Put Away")                
    elif (doc.doctype == 'Stock Entry' and doc.purpose == 'Material Transfer'):        
        create_warehouse_task(doc, "Pick Up")
        create_warehouse_task(doc, "Put Away")
    elif doc.doctype == 'Stock Entry' and doc.purpose == 'Material Issue':  # 其他出库
        create_warehouse_task(doc, "Pick Up")        
    else:
        if ((doc.doctype == "Delivery Note" and doc.items[0].qty > 0) or 
            (doc.doctype == "Purchase Receipt" and doc.items[0].qty < 0)):
            warehouse_task_type = "Pick Up"
        else:
            warehouse_task_type = "Put Away" 
        create_warehouse_task(doc, warehouse_task_type)

def create_warehouse_task(doc, warehouse_task_type = None ):
    def set_missing_values(source, target):
        target.warehouse_task_type = warehouse_task_type 
        target.voucher_type = source.doctype
        target.voucher_no = source.name

    def update_item(source, target, source_parent):
        """
        采购入库，销售出库，物料移动，库存调账明细表字段转化为任务单明细时字段名匹配有细节不同
        需在这里单独处理
        """
        if warehouse_task_type == "Pick Up":
            target.warehouse = source.get('s_warehouse') or source.get('warehouse')
        else:
            target.warehouse = source.get('t_warehouse') or source.get('warehouse')
        if source_parent.doctype == "Stock Reconciliation":
            target.uom = item_uom_map.get(target.item_code)
            target.stock_uom = target.uom
            target.qty = abs(flt(source.quantity_difference))
            target.stock_qty = target.qty
            target.conversion_factor = 1
        else:    
            target.stock_qty = source.get('stock_qty',0)    #stock_qty被设置为了不可复制            
            target.stock_uom = source.get('stock_uom')
            if hasattr(source, 'transfer_qty') and not target.stock_qty:
                target.stock_qty = source.transfer_qty
        target.batch_no = source.get('batch_no')            #被设置了不允许复制，所以代码赋值
        target.storage_bin = storage_bin_map.get(target.item_code)
    
    def item_condition(child_doc):
        result = False
        warehouse = (child_doc.get('s_warehouse') if warehouse_task_type == "Pick Up" 
            else child_doc.get('t_warehouse')) or child_doc.get('warehouse')
        if child_doc.doctype == "Stock Reconciliation Item":            
            if bool(warehouse in enable_storage_bin_warehouse):
                if warehouse_task_type == "Pick Up":
                    result = bool(flt(child_doc.quantity_difference) < 0)
                else:
                    result = bool(flt(child_doc.quantity_difference) > 0) 
        else:            
            result = bool(warehouse in enable_storage_bin_warehouse)
        return result

    if doc.doctype == "Stock Reconciliation":
        item_uom_map = frappe._dict(frappe.get_all(
            'Item',
            filters = {'name': ('in', {row.item_code for row in doc.items})},
            fields = ['name', 'stock_uom'],
            as_list = 1
        ))    
    storage_bin_map = frappe._dict(frappe.get_all('Item Default',
        filters = {'parent': ('in', {row.item_code for row in doc.items}),
                   'company': doc.company
        },
        fields =['parent','default_storage_bin'],
        as_list = 1
    ))
    enable_storage_bin_warehouse = frappe.get_all('Warehouse',
        filters = {'name': ('in', {(row.get('s_warehouse') or row.get('warehouse')) if warehouse_task_type == "Pick Up"
            else (row.get('t_warehouse') or row.get('warehouse')) for row in doc.items}),
                   'enable_storage_bin': 1
        },
        pluck ='name'
    )
    if enable_storage_bin_warehouse:
        child_doctype = f"{doc.doctype} {'Item' if doc.doctype != 'Stock Entry' else 'Detail'}"
        warehoue_task_doc = get_mapped_doc(
            doc.doctype,
            doc.name,
            {
                doc.doctype: {
                    "doctype": "Warehouse Task",
                    "validation": {"docstatus": ["=", 1]},
                },
                child_doctype: {
                    "doctype": "Warehouse Task Detail",
                    "postprocess": update_item,                                
                    "condition": item_condition
                },            
            },
            {},
            set_missing_values
        )
        if warehoue_task_doc.warehouse_task_type == "Pick Up":
            auto_assign_storage_bin(warehoue_task_doc)

        if warehoue_task_doc.items:
            warehoue_task_doc.insert(ignore_permissions = 1)

def auto_assign_storage_bin(doc):
    #基于库存数量，如果业务单位与库存单位不同，基于转换率转成业务单位

    idx = 1
    items = []
    storage_bin_assigned = False
    messages = []
    bin_qty_list = []
    key_tuple_no_batch = {(row.item_code, row.warehouse) for row in doc.items if not row.batch_no}
    if key_tuple_no_batch:
        bin_qty_list = get_bin_qty_list(key_tuple_no_batch)
    key_tuple_with_batch = {(row.item_code, row.warehouse, row.batch_no) for row in doc.items if row.batch_no}
    if key_tuple_with_batch:
        bin_qty_list.extend(get_bin_qty_list(key_tuple_with_batch))    
    for item in doc.items:
        unallocated_qty = item.stock_qty            
        allocated_qty, actual_qty = 0, 0
        #globals().update(locals()) 
        item_bin_qty_list = [r for r in bin_qty_list if 
            r.item_code==item.item_code and r.warehouse == item.warehouse
            and (not item.batch_no or r.batch_no == item.batch_no)]               
        for row in item_bin_qty_list or []:
            if row.qty<=0: continue
            storage_bin_assigned = True            
            if row.qty >= unallocated_qty:
                # 超出1%且数量5以内，允许超量出
                if ((row.qty - unallocated_qty) / item.stock_qty * 100 < 1) and (row.qty - unallocated_qty) < 5:
                    allocated_qty = row.qty
                else:
                    allocated_qty = unallocated_qty 
            else:
                allocated_qty = row.qty
            row.qty -= allocated_qty
            item.storage_bin = row.storage_bin
            unallocated_qty -= allocated_qty            
            actual_qty = row.qty
            new_item = frappe.copy_doc(item)
            #globals().update(locals())
            [new_item.set(field, item.get(field)) for field in item.meta.get_fieldnames_with_value()]
            new_item.batch_no = row.batch_id
            new_item.stock_qty = allocated_qty
            new_item.qty = item.stock_qty / (item.conversion_factor or 1)   #由库存单位转为业务单位
            new_item.idx = idx
            idx += 1
            items.append(new_item)            
            if unallocated_qty <= 0: break
        if unallocated_qty < item.stock_qty:
            if unallocated_qty > 0: messages.append(f'物料 {item.item_code} 货位可分派数量还欠 {unallocated_qty}')                
        if not allocated_qty:
            item.idx = idx
            idx += 1      
            items.append(item)
    if storage_bin_assigned:
        doc.items = items

def get_bin_qty_list(key_tuple):
    sbq_doctype = frappe.qb.DocType('Storage Bin Qty')
    query = frappe.qb.from_(sbq_doctype
    ).select(        
        sbq_doctype.item_code,
        sbq_doctype.batch_no,
        sbq_doctype.warehouse,
        sbq_doctype.storage_bin,                
        sbq_doctype.actual_qty.as_('qty')
    )
    #按有批号与无批号两种情况
    if len(list(key_tuple)[0]) == 2:
        query = query.where(Tuple(sbq_doctype.item_code, sbq_doctype.warehouse).isin(key_tuple))
    else:
        query = query.where(Tuple(sbq_doctype.item_code, sbq_doctype.warehouse, sbq_doctype.batch_no).isin(key_tuple))
    query.orderby(sbq_doctype.gr_date, sbq_doctype.gr_time)
    qty_list = query.run(as_dict=1)
    return qty_list